import React from 'react';
import { getKeys, Omit } from '../utils/utility';

interface IValidation {
  error?: boolean | string;
  warn?: boolean | string;
  success?: boolean | string;
}

type RuleFunction = (value: any, values: IValues) => IValidation;

export type Rule = RuleFunction | RegExp;

export interface IRules {
  [key: string]: Rule;
}

export interface IValues {
  [key: string]: any;
}

export interface IValidations {
  [key: string]: IValidation | undefined;
}

export interface IValidatorProps {
  validate: (...args: string[]) => void;
  setValues: (valuesGetter: () => any) => void;
  validations: IValidations;
  getError: (key: string) => boolean | string | undefined;
  getSuccess: (key: string) => boolean | string | undefined;
  getWarn: (key: string) => boolean | string | undefined;
  clearError: (...keys: string[]) => void;
}

export interface IValidatorState {
  validations: IValidations;
}

function withValidator(rules: IRules) {
  return <T extends IValidatorProps>(Component: React.ComponentType<T>) => (
    class extends React.Component<Omit<T, keyof IValidatorProps>, IValidatorState> {
      public state = { validations: {} as IValidations };

      constructor(props: Omit<T, keyof IValidatorProps>) {
        super(props);
        this.setValues = this.setValues.bind(this);
        this.validate = this.validate.bind(this);
        this.getError = this.getError.bind(this);
        this.getSuccess = this.getSuccess.bind(this);
        this.getWarn = this.getWarn.bind(this);
        this.clearError = this.clearError.bind(this);
        this.transformRule = this.transformRule.bind(this);
      }

      public getValues = (): IValues => {
        // tslint:disable-next-line
        console.warn('校验前请先调用用setValues方法');
        return {};
      }

      public setValues(valuesGetter: () => IValues) {
        this.getValues = valuesGetter;
      }

      public transformRule(rule: Rule): RuleFunction {
        if (rule instanceof RegExp) {
          return (value: any) => {
            const isMatch = rule.test(value);
            return {
              success: isMatch,
              error: !isMatch,
            };
          };
        }
        return rule;
      }

      public validate(...args: string[]): IValidations {
        const values = this.getValues();
        const keys = args.length ? args : getKeys(values);
        const validations = keys.reduce((results: IValidations, key) => {
          const rule = rules[key];
          if (rule) {
            const value = values[key];
            results[key] = this.transformRule(rule)(value, values);
          }
          return results;
        }, {});
        this.setState({
          validations: {
            ...this.state.validations,
            ...validations,
          },
        });
        return validations;
      }

      public getError(key: string) {
        const validation = this.state.validations[key];
        return validation && validation.error;
      }

      public getWarn(key: string) {
        const validation = this.state.validations[key];
        return validation && validation.warn;
      }

      public getSuccess(key: string) {
        const validation = this.state.validations[key];
        return validation && validation.success;
      }

      public clearError(...keys: string[]) {
        if (keys.length) {
          const newState = keys.reduce((state: IValidations, key) => {
            state[key] = {};
            return state;
          }, {});
          this.setState({
            validations: {
              ...this.state.validations,
              ...newState,
            },
          });
        } else {
          this.setState({ validations: {} });
        }
      }

      public render() {
        return <Component
          validate={this.validate}
          setValues={this.setValues}
          validations={this.state.validations}
          getError={this.getError}
          getWarn={this.getWarn}
          getSuccess={this.getSuccess}
          clearError={this.clearError}
          {...this.props as T}
        />;
      }
    }
  );
}

export default withValidator;
